/*
* Copyright (c) Huawei Technologies Co., Ltd. 2020-2021. All rights reserved.
* gazelle is licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*     http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
*/

#include <securec.h>

#include <netinet/in.h>

#include <rte_mempool.h>
#include <rte_memzone.h>
#include <rte_jhash.h>
#include <rte_rwlock.h>
#include <rte_lcore.h>

#include "lstack_log.h"
#include "lstack_conn_htable.h"

#define CONN_HTABLE_NAME   "conn_htable"
#define CONN_HTABLE_MMEPOOL_NAME  "conn_htable_pool"

static struct conn_htable *g_conn_htable = NULL;
static struct rte_mempool *g_conn_mempool = NULL;

struct conn_htable *conn_htable_create(int max_conn_num)
{
    struct conn_htable *conn_htable = NULL;
    uint32_t i;
    const struct rte_memzone *mz;
    char name[RTE_MEMZONE_NAMESIZE] = {0};

    snprintf_s(name, sizeof(name), sizeof(name) - 1, CONN_HTABLE_NAME);
    mz = rte_memzone_reserve(name, sizeof(struct conn_htable), rte_socket_id(), 0);
    if (mz == NULL) {
        LSTACK_LOG(ERR, LSTACK, "%s create failed\n", CONN_HTABLE_NAME);
        return NULL;
    }
    g_conn_mempool = rte_mempool_create(CONN_HTABLE_MMEPOOL_NAME, max_conn_num, sizeof(struct conn_htable_node),
                                        0, 0, NULL, NULL, NULL, NULL, rte_socket_id(), 0);
    if (g_conn_mempool == NULL) {
        LSTACK_LOG(ERR, LSTACK, "%s create failed, errno is %d\n", CONN_HTABLE_MMEPOOL_NAME, errno);
        rte_memzone_free(mz);
        return NULL;
    }
    memset(mz->addr, 0, mz->len);
    conn_htable = mz->addr;

    rte_rwlock_init(&conn_htable->rwlock);

    for (i = 0; i < GAZELLE_MAX_CONN_HTABLE_SIZE; i++) {
        hlist_init_head(&conn_htable->array[i].chain);
        conn_htable->array[i].chain_size = 0;
    }
    conn_htable->cur_conn_num = 0;
    conn_htable->max_conn_num = max_conn_num;
    g_conn_htable = conn_htable;

    LSTACK_LOG(ERR, LSTACK, "%s create success\n", CONN_HTABLE_NAME);
    return conn_htable;
}

int conn_htable_attach(void)
{
    const struct rte_memzone *mz;
    mz = rte_memzone_lookup(CONN_HTABLE_NAME);
    if (mz == NULL) {
        LSTACK_LOG(ERR, LSTACK, "%s attach failed\n", CONN_HTABLE_NAME);
        return -1;
    }
    g_conn_htable = mz->addr;

    g_conn_mempool = rte_mempool_lookup(CONN_HTABLE_MMEPOOL_NAME);
    if (g_conn_mempool == NULL) {
        LSTACK_LOG(ERR, LSTACK, "%s attach failed\n", CONN_HTABLE_MMEPOOL_NAME);
        return -1;
    }

    LSTACK_LOG(ERR, LSTACK, "%s attach success\n", CONN_HTABLE_NAME);

    return 0;
}

void conn_htable_destroy(void)
{
    const struct rte_memzone *mz;
    struct rte_mempool *mempool;
    struct hlist_node *node = NULL;
    struct conn_htable_node *conn = NULL;
    uint32_t i;
    struct conn_htable *conn_htable = g_conn_htable;

    if (conn_htable == NULL) {
        return;
    }

    for (i = 0; i < CONN_HTABLE_MAX_SIZE; i++) {
        node = conn_htable->array[i].chain.first;
        while (node != NULL) {
            conn = hlist_entry(node, typeof(*conn), conn_node);
            node = node->next;
            hlist_del_node(&conn->conn_node);
            rte_mempool_put(g_conn_mempool, conn);
        }
    }

    mz = rte_memzone_lookup(CONN_HTABLE_NAME);
    if (mz != NULL) {
        rte_memzone_free(mz);
    }

    mempool = rte_mempool_lookup(CONN_HTABLE_MMEPOOL_NAME);
    if (mempool != NULL) {
        rte_mempool_free(mempool);
    }
}

static struct conn_htable_hbucket *conn_htable_hbucket_get(struct conn_htable *conn_htable, struct gazelle_quintuple *quintuple)
{
    uint32_t index;
//    index = tuple_hash_fn(quintuple->src_ip.u_addr.ip4.addr, quintuple->src_port, quintuple->dst_ip.u_addr.ip4.addr,
//                            quintuple->dst_port) % GAZELLE_MAX_CONN_HTABLE_SIZE;

    index = rte_jhash(quintuple, sizeof(*quintuple), 0) % CONN_HTABLE_MAX_SIZE;
    return &conn_htable->array[index];
}

static struct conn_htable_node *conn_htable_get_by_quintuple(struct gazelle_quintuple *quintuple, uint16_t cur_queue_id)
{
    struct conn_htable *conn_htable = g_conn_htable;
    struct conn_htable_node *conn = NULL;
    struct conn_htable_hbucket *conn_hbucket = NULL;
    struct hlist_node *node = NULL;
    struct hlist_head *head = NULL;

    conn_hbucket = conn_htable_hbucket_get(conn_htable, quintuple);
    if (conn_hbucket == NULL) {
        LSTACK_LOG(ERR, LSTACK, "can't get conn_hbucket\n");
        return NULL;
    }

    rte_rwlock_read_lock(&conn_htable->rwlock);
    head = &conn_hbucket->chain;
    hlist_for_each_entry(conn, node, head, conn_node) {
        if (memcmp(&conn->quintuple, quintuple, sizeof(struct gazelle_quintuple)) == 0) {
            rte_rwlock_read_unlock(&conn_htable->rwlock);
            return conn;
        }
    }

    rte_rwlock_read_unlock(&conn_htable->rwlock);
    return NULL;
}

int conn_htable_get_queue_by_quintuple(struct gazelle_quintuple *quintuple, uint16_t cur_queue_id)
{
    struct conn_htable_node *conn = conn_htable_get_by_quintuple(quintuple, cur_queue_id);
    if (conn == NULL) {
        return -1;
    } else {
        return conn->queue_id;
    }
}

struct conn_htable_node *conn_htable_add_by_quintuple(struct gazelle_quintuple *quintuple, uint16_t queue_id)
{
    struct conn_htable *conn_htable = g_conn_htable;
    int32_t ret;
    struct conn_htable_hbucket *conn_hbucket = NULL;
    struct conn_htable_node *conn = NULL;

    conn = conn_htable_get_by_quintuple(quintuple, queue_id);
    if (conn != NULL) {
        LSTACK_LOG(ERR, LSTACK, "tcp conn is not null???\n");
        return conn;
    }

    conn_hbucket = conn_htable_hbucket_get(conn_htable, quintuple);
    if (conn_hbucket == NULL) {
        LSTACK_LOG(ERR, LSTACK, "can't get conn hbucket\n");
        return NULL;
    }

    ret = rte_mempool_get(g_conn_mempool, (void **)&conn);
    if (ret < 0) {
        LSTACK_LOG(ERR, LSTACK, "alloc conn failed\n");
        return NULL;
    }

    rte_rwlock_write_lock(&conn_htable->rwlock);
    if (conn_htable->cur_conn_num >= conn_htable->max_conn_num) {
        rte_rwlock_write_unlock(&conn_htable->rwlock);
        rte_mempool_put(g_conn_mempool, conn);
        LSTACK_LOG(ERR, LSTACK, "max conn num(%d) reached!\n", conn_htable->max_conn_num);
        return NULL;
    }

    ret = memcpy_s(&conn->quintuple, sizeof(struct gazelle_quintuple), quintuple, sizeof(*quintuple));
    if (ret != 0) {
        rte_rwlock_write_unlock(&conn_htable->rwlock);
        rte_mempool_put(g_conn_mempool, conn);
        return NULL;
    }

    conn->queue_id = queue_id;

    hlist_add_head(&conn->conn_node, &conn_hbucket->chain);
    conn_htable->cur_conn_num++;
    conn_hbucket->chain_size++;
    rte_rwlock_write_unlock(&conn_htable->rwlock);

    return conn;
}

void conn_htable_del_by_quintuple(struct gazelle_quintuple *quintuple)
{
    struct conn_htable *conn_htable = g_conn_htable;
    struct conn_htable_node *conn = NULL;
    struct conn_htable_hbucket *conn_hbucket = NULL;
    struct hlist_node *node = NULL;
    struct hlist_head *head = NULL;

    conn_hbucket = conn_htable_hbucket_get(conn_htable, quintuple);
    if (conn_hbucket == NULL) {
        LSTACK_LOG(ERR, LSTACK, "can't get conn_hbucket\n");
        return;
    }

    rte_rwlock_write_lock(&conn_htable->rwlock);
    head = &conn_hbucket->chain;
    hlist_for_each_entry(conn, node, head, conn_node) {
        if (memcmp(&conn->quintuple, quintuple, sizeof(struct gazelle_quintuple)) == 0) {
            break;
        }
    }

    if (conn == NULL) {
        LSTACK_LOG(ERR, LSTACK, "can't del conn_htable_node\n");
        rte_rwlock_write_unlock(&conn_htable->rwlock);
        return;
    }

    hlist_del_node(&conn->conn_node);
    conn_htable->cur_conn_num--;
    conn_hbucket->chain_size--;
    rte_rwlock_write_unlock(&conn_htable->rwlock);
    rte_mempool_put(g_conn_mempool, conn);
}

void conn_htable_del_by_queue(uint16_t queue_id)
{
    struct conn_htable *conn_htable = g_conn_htable;
    struct conn_htable_node *conn = NULL;
    struct conn_htable_hbucket *conn_hbucket = NULL;
    struct hlist_node *node = NULL;
    struct hlist_head *head = NULL;

    rte_rwlock_write_lock(&conn_htable->rwlock);
    for (int i = 0; i < CONN_HTABLE_MAX_SIZE; i++) {
        conn_hbucket = &conn_htable->array[i];
        if (conn_hbucket->chain_size == 0) {
            continue;
        }

        hlist_for_each_entry(conn, node, head, conn_node) {
            if (conn->queue_id == queue_id) {
                hlist_del_node(&conn->conn_node);
                conn_htable->cur_conn_num--;
                conn_hbucket->chain_size--;
                rte_mempool_put(g_conn_mempool, conn);
            }
        }
    }

    rte_rwlock_write_unlock(&conn_htable->rwlock);
}

void conn_htable_dump(void)
{
    struct conn_htable *conn_htable = g_conn_htable;
    struct conn_htable_hbucket *conn_hbucket = NULL;
    struct hlist_node *node = NULL;
    struct hlist_head *head = NULL;
    struct conn_htable_node *conn = NULL;
    int count = 0;

    rte_rwlock_read_lock(&conn_htable->rwlock);
    for (int i = 0; i < GAZELLE_MAX_CONN_HTABLE_SIZE; i++) {
        conn_hbucket = &conn_htable->array[i];
        head = &conn_hbucket->chain;
        hlist_for_each_entry(conn, node, head, conn_node) {
            printf("conn src port is %d, dst port is %d, queue is %d, protocol is %d\n", ntohs(conn->quintuple.src_port), ntohs(conn->quintuple.dst_port), conn->queue_id, conn->quintuple.protocol);
            count++;
        }
    }
    rte_rwlock_read_unlock(&conn_htable->rwlock);

    printf("conn htbale count is %d\n", count);
}
